﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace App10.Common
{
    public interface IModel
    {
        Dictionary<String, IProperty> Properties { get; }
        ObservableCollection<string> Errors { get; }
        Action<IModel> Validator { get; set; }
        bool IsValid { get; }
        bool IsDirty { get; }
        bool Validate();
        void Revert();
    }

    public abstract class ModelBase<T> : BindableBase, IModel where T : IModel
    {
        public ModelBase()
        {
            foreach (var property in this.GetType().GetRuntimeProperties())
            {
                var type = typeof(Property<>).MakeGenericType(property.PropertyType);
                var prop = (IProperty)Activator.CreateInstance(type);
                this.Properties.Add(property.Name, prop);
                prop.ValueChanged += (s, e) =>
                {
                    // whenever a property is changed, bubble changed event
                    this.RaisePropertyChanged(property.Name);
                    // whenever a property is changed, validate the model
                    Validate();
                };
            }
        }

        protected Dictionary<String, IProperty> _Properties = new Dictionary<string, IProperty>();
        // [SQLite.Ignore]
        public Dictionary<String, IProperty> Properties { get { return _Properties; } }

        protected void SetProperty<TT>(TT value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
        {
            (this.Properties[propertyName] as Property<TT>).Value = value;
            this.RaisePropertyChanged(propertyName);
        }
        protected TT GetProperty<TT>([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null) { return (this.Properties[propertyName] as Property<TT>).Value; }

        ObservableCollection<string> _Errors = new ObservableCollection<string>();
        // [SQLite.Ignore]
        public ObservableCollection<string> Errors { get { return _Errors; } }

        bool _IsValid = true;
        // [SQLite.Ignore]
        public bool IsValid { get { return _IsValid; } set { SetProperty(ref _IsValid, value); } }

        bool _IsDirty = false;
        // [SQLite.Ignore]
        public bool IsDirty { get { return _IsDirty; } set { base.SetProperty(ref _IsDirty, value); } }

        Action<IModel> _Validator = default(Action<IModel>);
        // [SQLite.Ignore]
        public Action<IModel> Validator { get { return _Validator; } set { SetProperty(ref _Validator, value); } }

        public void Revert()
        {
            foreach (var property in Properties.Values)
                property.Revert();
        }

        public bool Validate()
        {
            var properties = Properties.Values;
            foreach (var property in properties)
            {
                property.Errors.Clear();
            }
            this.Errors.Clear();
            if (Validator != null)
                Validator.Invoke(this as IModel);
            foreach (var error in properties.SelectMany(x => x.Errors))
            {
                this.Errors.Add(error);
            }
            IsDirty = properties.Any(x => x.IsDirty);
            return IsValid = (properties.Any(x => !x.IsValid)) ? false : !this.Errors.Any();
        }
    }
}
